//
// Created by root on 11/7/18.
//

//#include <stdio.h>
#include <cstdlib>

#include "endpoints.h"
#include "networking.h"
#include "anysocket.h"
#include "log.h"

#define MAX_ENDPOINTS 32

typedef struct {
    uint32_t ip;
    uint16_t port;
} endpoint_t;

struct EndPoints{
    endpoint_t endpoints[MAX_ENDPOINTS];
    int endpoints_nb;
    int current;
};

EndPoints_t all_managers;
EndPoints_t all_redis_servers;

void add_a_manager(uint32_t ip, uint16_t port)
{
    add_a_endpoint(&all_managers, ip, port);
}

void parse_managers(const char *str_ep)
{
    parse_endpoints(str_ep, &all_managers, 9000);
    get_an_endpoint(&all_managers, &manager_ip, &manager_port);
}

static void get_current(EndPoints_t * eps, uint32_t *ip, uint16_t *port)
{
    *ip = eps->endpoints[eps->current].ip;
    *port = eps->endpoints[eps->current].port;
}

st_netfd_t get_a_connection(EndPoints_t *eps, uint32_t *ip, uint16_t *port)
{
    st_netfd_t stfd;
    get_current(eps, ip, port);
    for(int retry = 0; retry < 3; retry++) {
        for(int i = 0; i < eps->endpoints_nb * 2; i++) {
            stfd = ursax_connect(eps->endpoints[eps->current].ip,
                    eps->endpoints[eps->current].port, false);
            if(!stfd) {
                get_an_endpoint(eps, ip, port);
            }
            else {
                return stfd;
            }
        }
        LOG_INFO("failed to connect and sleep %d retry", (1<<retry) * 100 * 1000);
        st_usleep((1<<retry) * 100 * 1000);
    }
    return NULL;
}

st_netfd_t get_a_manager_connection(void)
{
    return get_a_connection(&all_managers, &manager_ip, &manager_port);
}

void print_endpoints(EndPoints_t *eps) {
    char buf[MAX_ENDPOINTS * 24];
    int n = 0;
    for(int i = 0; i < eps->endpoints_nb; i++) {
        n += sprintf(buf+n, "%s:%d ", str_ip(eps->endpoints[i].ip), eps->endpoints[i].port);
    }
    LOG_INFO("all endpoints %d: %s", eps->endpoints_nb, buf);
}

void add_a_endpoint(EndPoints_t *eps, uint32_t ip, uint16_t port)
{
    for(int i = 0; i < eps->endpoints_nb; i++) {
        if(eps->endpoints[i].ip == ip && eps->endpoints[i].port == port) {
            return;
        }
    }

    if(eps->endpoints_nb < MAX_ENDPOINTS){
        eps->endpoints[eps->endpoints_nb++] = (endpoint_t){ip, port};
    }else{
        LOG_ERROR("err eps is full");
    }
    print_endpoints(eps);
}

// ip:port,ip:port,ip:port...
void parse_endpoints(const char *str_eps, EndPoints_t * eps, uint16_t default_port)
{
    if(!str_eps){
        LOG_INFO("%s NULL str_eps, ret", __func__);
        return;
    }
    char *dump_str = strdup(str_eps);
    char *saveptr = NULL;
    char *p = strtok_r(dump_str, ",", &saveptr);
    while(p){
        LOG_DEBUG("strtok_r ret value %s", p);
        uint32_t ip;
        uint16_t port;
        int ret = parse_ip_port(p, &ip, &port, default_port);
        if(0 != ret){
            LOG_FATAL("error parse ip and port");
            exit(-1);
        }
        add_a_endpoint(eps, ip, port);

        p = strtok_r(NULL, ",", &saveptr);
    }

    print_endpoints(eps);
    free(dump_str);
}

int get_an_endpoint(EndPoints_t * eps, uint32_t *ip, uint16_t *port)
{
    if(0 == eps->endpoints_nb){
        *ip = 0;
        *port = 0;
        return -1;
    }

    eps->current = rand() % eps->endpoints_nb;
    get_current(eps, ip, port);
    return 0;
}

int get_a_redis_server_str(char *ip, uint16_t *port)
{
    uint32_t ip_int;
    get_an_endpoint(&all_redis_servers, &ip_int, port);

    struct in_addr addr;
    memset(&addr, 0, sizeof(addr));
    addr.s_addr = htonl(ip_int);

    strcpy(ip, inet_ntoa(addr));

    // LOG_DEBUG("get_a_redis_server_str got %s:%d", ip, *port);
    return 0;
}

void parse_redis_servers(const char *str_ep)
{
    parse_endpoints(str_ep, &all_redis_servers, 6379);
    get_a_redis_server_str(redis_ip, &redis_port);
}

void
remove_endpoint_from_redis_servers(int index)
{
    int cnt = 0;

    for (cnt = index+1; cnt < all_redis_servers.endpoints_nb; cnt++) {
        all_redis_servers.endpoints[cnt-1] = all_redis_servers.endpoints[cnt];
    }

    all_redis_servers.endpoints_nb -= 1;
    all_redis_servers.current = 0;
}

int get_redis_servers_nb(void)
{
    return get_endpoints_nb(&all_redis_servers);
}

int get_endpoints_nb(EndPoints_t *eps)
{
    return eps->endpoints_nb;
}

bool
do_redis_servers_contain_port(uint16_t port, int *index)
{
    int cnt = 0;
    for (cnt = 0; cnt < all_redis_servers.endpoints_nb; cnt++) {
        if (all_redis_servers.endpoints[cnt].port == port) {
            *index = cnt;
            return true;
        }
    }

    return false;
}
